﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.Common;
using System.Text;

/*
 * 为什么不采用 DbProviderFactory 进行抽象呢。原因是“在Oracle接口上，Command需要一些自定义”。但也考虑采用 DbProviderFactory 方式进行部分的抽象。
 */
namespace Sofire.Data
{
    /// <summary>
    /// 数据库操作引擎的基类。
    /// </summary>
    public abstract partial class QueryEngineBase : ExecuteEventObjectEngineBase<QueryEngineManager, ExecuteCommand>, IQueryEngine
    {
        private string _ConnectionString;
        private QueryEngineProvider _Provider;

        /// <summary>
        /// 初始化 <see cref="Sofire.Data.QueryEngineBase"/> 的新实例。
        /// </summary>
        protected QueryEngineBase() { }

        /// <summary>
        /// 指定 <paramref name="provider"/> 和 <paramref name="connectionString"/>，初始化 <see cref="Sofire.Data.QueryEngineBase"/> 的新实例。
        /// </summary>
        /// <param name="provider">提供程序。</param>
        /// <param name="connectionString">连接字符串。</param>
        protected QueryEngineBase(QueryEngineProvider provider, string connectionString)
        {
            this.Initialization(provider, connectionString);
        }

        /// <summary>
        /// 设置或获取一个值，表示当前数据源的连接字符串。
        /// </summary>
        public string ConnectionString
        {
            get
            {
                if(this._ConnectionString == null) return string.Empty;
                return this._ConnectionString;
            }
            set
            {
                this._ConnectionString = value;
            }
        }

        /// <summary>
        /// 获取一个值，表示当前数据操作的数据提供程序的类型。
        /// </summary>
        public QueryEngineProvider Provider
        {
            get
            {
                return this._Provider;
            }
        }

        /// <summary>
        /// 创建并返回一个新的查询上下文。
        /// </summary>
        public virtual QueryContext Context
        {
            get
            {
                return new QueryContext(this);
            }
        }

        private const string DEFAULT_PREFIX = "@";
        /// <summary>
        /// 获取参数名称的前缀（T-SQL 文本）。
        /// </summary>
        public virtual string ParameterPrefixWithText
        {
            get
            {
                return DEFAULT_PREFIX;
            }
        }
        /// <summary>
        /// 获取参数名称的前缀（参数集合）。在 Oracle 中，并不需要指定参数名称的前缀。
        /// </summary>
        public virtual string ParameterPrefixWithCollection
        {
            get
            {
                return DEFAULT_PREFIX;
            }
        }

        /// <summary>
        /// 实例化一个指定数据源的命令。
        /// </summary>
        /// <param name="conn">指定数据源。</param>
        /// <returns>返回一个新创建的命令。</returns>
        protected virtual DbCommand NewCommand(DbConnection conn)
        {
            return conn.CreateCommand();
        }

        /// <summary>
        /// 创建并返回一个与给定数据连接关联的命令对象。
        /// </summary>
        /// <param name="conn">指定数据源。</param>
        /// <param name="commandText">Transact-SQL 语句。第一个字符为“>”时，表示一个存储过程。</param>
        /// <returns>返回一个命令对象。</returns>
        protected virtual DbCommand CreateCommand(DbConnection conn, string commandText)
        {
            if(string.IsNullOrEmpty(commandText)) throw new ArgumentNullException("commandText");
            DbCommand comm = this.NewCommand(conn);
            var firstChar = commandText[0];
            if(firstChar == '>')//存储过程
            {
                commandText = commandText.Remove(0, 1);
                comm.CommandType = CommandType.StoredProcedure;
            }
            comm.CommandText = commandText;
            return comm;
        }

        /// <summary>
        /// 创建并返回一个与当前数据源关联的命令对象。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <returns>返回一个命令对象。</returns>
        protected DbCommand CreateCommand(ExecuteCommand command)
        {
            return this.CreateCommand(this.CreateConnection(), command);
        }

        /// <summary>
        /// 创建并返回一个与给定数据源关联的命令对象。
        /// </summary>
        /// <param name="conn">指定数据源。</param>
        /// <param name="command">查询命令。</param>
        /// <returns>返回一个命令对象。</returns>
        public virtual DbCommand CreateCommand(DbConnection conn, ExecuteCommand command)
        {
            string commandText = command.ToString();
            DbCommand comm = this.CreateCommand(conn, commandText);
            if(command.Parameters.Count > 0)
                this.FillParameters(comm, command.Parameters);
            comm.CommandTimeout = command.Timeout;
            command.SetRuntimeObject(comm);
            return comm;
        }

        /// <summary>
        /// 描述指定的 <see cref="System.Data.Common.DbParameter"/>。
        /// </summary>
        /// <param name="parameter">一个 <see cref="System.Data.Common.DbParameter"/> 的实例。</param>
        /// <returns>返回参数描述的 <see cref="System.String"/> 值。</returns>
        public abstract string DescribeParameter(DbParameter parameter);

        /// <summary>
        /// 将参数集合填充到 <see cref="System.Data.Common.DbCommand"/>。
        /// </summary>
        /// <param name="command">命令对象。</param>
        /// <param name="parameters">参数集合。</param>
        protected virtual void FillParameters(DbCommand command, ExecuteParameterCollection parameters)
        {
            foreach(var p in parameters)
            {
                if(p.IsValid) command.Parameters.Add(p.CreateParameter(command));
            }
        }

        /// <summary>
        /// 创建并返回一个查询命令生成器。
        /// </summary>
        /// <returns>返回一个查询命令生成器。</returns>
        public abstract DbCommandBuilder CreateCommandBuilder();

        /// <summary>
        /// 创建并返回一个到数据库的连接。
        /// </summary>
        /// <returns>返回一个到数据库的连接。</returns>
        public abstract DbConnection CreateConnection();

        /// <summary>
        /// 创建并返回一个数据适配器。
        /// </summary>
        /// <param name="command">命令对象。</param>
        /// <returns>返回一个数据适配器。</returns>
        public abstract DbDataAdapter CreateDataAdapter(DbCommand command);

        /// <summary>
        /// 指定查询命令，返回没有数据的 <see cref="System.Data.DataTable"/>。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <returns>返回没有数据的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetNoDataTable(ExecuteCommand command)
        {
            var dbCommand = this.CreateCommand(command);

            try
            {
                dbCommand.Connection.Open();
                DataTable table = new DataTable("SchemaTable");
                using(var r = dbCommand.ExecuteReader())
                {
                    for(int i = 0 ; i < r.FieldCount ; i++)
                    {
                        table.Columns.Add(r.GetName(i), r.GetFieldType(i));
                    }
                    return table;
                }
            }
            catch(Exception ex)
            {
                return ex;
            }
            finally
            {
                dbCommand.Connection.TryClose();
            }
        }

        /// <summary>
        /// 指定存储过程的名称，获取存储过程的参数名集合。
        /// </summary>
        /// <param name="procedureName">存储过程的名称。</param>
        /// <returns>返回一组表示存储过程参数名集合的数组。</returns>
        public virtual Result<string[]> GetProcedureParameters(string procedureName)
        {
            return "当前引擎不支持获取存储过程的参数！";
        }

        /// <summary>
        /// 返回此 <see cref="System.Data.Common.DbConnection"/> 的数据源的架构信息。
        /// </summary>
        /// <returns>包含架构信息的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetSchema()
        {
            return this.GetSchema(null, null);
        }

        /// <summary>
        /// 使用表示架构名称的指定字符串返回此 <see cref="System.Data.Common.DbConnection"/> 的数据源的架构信息。
        /// </summary>
        /// <param name="collectionName">指定要返回的架构的名称。</param>
        /// <returns>包含架构信息的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetSchema(string collectionName)
        {
            return this.GetSchema(collectionName, null);
        }

        /// <summary>
        /// 使用表示架构名称的指定字符串以及表示限制值的指定字符串数组返回此 <see cref="System.Data.Common.DbConnection"/> 的数据源的架构信息。
        /// </summary>
        /// <param name="collectionName">指定要返回的架构的名称。</param>
        /// <param name="restrictionValues">为请求的架构指定一组限制值。</param>
        /// <returns>包含架构信息的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetSchema(string collectionName, string[] restrictionValues)
        {
            var conn = this.CreateConnection();
            try
            {
                conn.Open();
                return conn.GetSchema(string.IsNullOrEmpty(collectionName) ? DbMetaDataCollectionNames.MetaDataCollections : collectionName, restrictionValues);
            }
            catch(Exception ex)
            {
                return ex;
            }
            finally
            {
                conn.TryClose();
            }
        }

        //http://msdn.microsoft.com/zh-cn/library/system.data.sqlclient.sqldatareader.getschematable(v=VS.90).aspx
        /// <summary>
        /// 指定查询命令，返回结果的表结构。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <returns>一个描述列元数据的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetSchemaTable(ExecuteCommand command)
        {
            return this.GetSchemaTable(command, CommandBehavior.Default);
        }

        /// <summary>
        /// 指定查询命令，返回结果的表结构。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <param name="behavior"><see cref="System.Data.CommandBehavior"/> 值之一。</param>
        /// <returns>一个描述列元数据的 <see cref="System.Data.DataTable"/>。</returns>
        public Result<DataTable> GetSchemaTable(ExecuteCommand command, CommandBehavior behavior)
        {
            var dbCommand = this.CreateCommand(command);
            try
            {
                dbCommand.Connection.Open();
                using(var r = dbCommand.ExecuteReader(behavior))
                {
                    return r.GetSchemaTable();
                }
            }
            catch(Exception ex)
            {
                return ex;
            }
            finally
            {
                dbCommand.Connection.TryClose();
            }
        }

        /// <summary>
        /// 指定查询命令，返回结果的表结构的字段名集合。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <returns>返回一组结果结构的列名的数组。</returns>
        public Result<string[]> GetSchemaTableColumnNames(ExecuteCommand command)
        {
            return this.GetSchemaTableColumnNames(command, CommandBehavior.Default);
        }

        /// <summary>
        /// 指定查询命令，返回结果的表结构的字段名集合。
        /// </summary>
        /// <param name="command">查询命令。</param>
        /// <param name="behavior"><see cref="System.Data.CommandBehavior"/> 值之一。</param>
        /// <returns>返回一组结果结构的列名的数组。</returns>
        public Result<string[]> GetSchemaTableColumnNames(ExecuteCommand command, CommandBehavior behavior)
        {
            var dbCommand = this.CreateCommand(command);

            try
            {
                dbCommand.Connection.Open();
                using(var r = dbCommand.ExecuteReader(behavior))
                {
                    string[] array = new string[r.FieldCount];
                    for(int i = 0 ; i < r.FieldCount ; i++)
                    {
                        array[i] = r.GetName(i);
                    }
                    return array;
                }
            }
            catch(Exception ex)
            {
                return ex;
            }
            finally
            {
                dbCommand.Connection.TryClose();
            }
        }

        /// <summary>
        /// 测试数据源的连接。
        /// </summary>
        public Result TestConnection()
        {
            Result r = new Result();

            DbConnection conn = null;
            try
            {
                using(conn = this.CreateConnection())
                {
                    conn.Open();
                }
            }
            catch(Exception ex)
            {
                r.ToFailded(ex);
            }
            finally
            {
                if(conn != null)
                {
                    conn.TryClose();
                }
            }
            return r;
        }

        /// <summary>
        /// 初始化查询引擎。
        /// </summary>
        /// <param name="provider">提供程序。</param>
        /// <param name="connectionString">连接字符串。</param>
        protected virtual void Initialization(QueryEngineProvider provider, string connectionString)
        {
            this._Provider = provider;
            this._ConnectionString = connectionString;
        }
    }
}